#include "FileArchive.h"
#include <stdio.h>
#include <assert.h>
#include <basetsd.h>


index g_MaxBlockIndex = ~0;
const unsigned long g_DataBufferLength = FILEARCHIVE_BLOCK_SIZE - sizeof(index)* 2;
std::vector<CFileArchive*> CFileArchive::_Archives;

CFileHandler::CFileHandler(CFileArchive* Archive, index StartIndex, CFileInfo* FileInfo) : _FileArchive(Archive), _Index(0), _FileInfo(FileInfo)
{
	_FileArchive->read(&_Buffer, StartIndex);
	_IndexBuffer.push_back(StartIndex);
	if (_Buffer._Next != 0){
		_IndexBuffer.push_back(_Buffer._Next);
	}

	_IsBuffered = false;
	_CurrentPos = 0;
}

size_t CFileHandler::write(const void* SrcBuf, unsigned long Size)
{
	const char* cSrcBuf = (const char*)SrcBuf;
	unsigned long CurrenBlockRest = g_DataBufferLength - _CurrentPos;
	unsigned long StartSize = Size;
	unsigned long FinishedCount = 0;
	while (Size > 0)
	{
		if (_CurrentPos == g_DataBufferLength){
			index NextBlock = _FileArchive->write(&_Buffer, _IndexBuffer[_Index], true);
			_IsBuffered = false;
			if (_Index == _IndexBuffer.size() - 1){
				_IndexBuffer.push_back(NextBlock);
			}
			_Index++;
			_CurrentPos = 0;
			CurrenBlockRest = g_DataBufferLength;
		}
		else{
			_IsBuffered = true;
		}
		unsigned long WriteCount = Size < CurrenBlockRest ? Size : CurrenBlockRest;
		memmove(&_Buffer._Data[_CurrentPos], cSrcBuf + FinishedCount, WriteCount);
		FinishedCount += WriteCount;
		_CurrentPos += WriteCount;
		Size -= WriteCount;
		CurrenBlockRest -= WriteCount;
		if (_Buffer._End < _CurrentPos){
			_Buffer._End = _CurrentPos;
		}
	}
	return StartSize;
}

size_t CFileHandler::read(void* DstBuf, unsigned long Size)
{
	char* cDstBuf = (char*)DstBuf;
	unsigned long CurrenBlockRest = _Buffer._End - _CurrentPos;
	unsigned long StartSize = Size;
	unsigned long FinishedCount = 0;
	while (Size > 0)
	{
		if (CurrenBlockRest > 0){
			unsigned long ReadCount = Size < CurrenBlockRest ? Size : CurrenBlockRest;
			memmove(cDstBuf + FinishedCount, &_Buffer._Data[_CurrentPos], ReadCount);
			_CurrentPos += ReadCount;
			FinishedCount += ReadCount;
			Size -= ReadCount;
		}
		else if (_IndexBuffer.size() - 1 > _Index){
			if (_IsBuffered){
				_FileArchive->write(&_Buffer, _IndexBuffer[_Index]);
				_IsBuffered = false;
			}
			_FileArchive->read(&_Buffer, _Buffer._Next);
			if (_Buffer._Next){
				_IndexBuffer.push_back(_Buffer._Next);
			}
			_CurrentPos = 0;
			_Index++;
		}
		else{
			return FinishedCount;
		}
		CurrenBlockRest = _Buffer._End - _CurrentPos;
	}
	return StartSize;
}

size_t CFileHandler::size()
{
	_FileArchive->getBlocks(_IndexBuffer, g_MaxBlockIndex);
	if (_IsBuffered && (_IndexBuffer.size() - 1) != _Index){
		_FileArchive->write(&_Buffer, _IndexBuffer[_Index]);
		_IsBuffered = false;
	}

	if ((_IndexBuffer.size() - 1) == _Index){
		return(_Index * g_DataBufferLength + _Buffer._End);
	}
	else{
		CBlock Block;
		_FileArchive->read(&Block, _IndexBuffer.back());
		return((_IndexBuffer.size() - 1) * g_DataBufferLength + _Buffer._End);
	}
}

size_t CFileHandler::setPos(unsigned long Position)
{
	index BlocksCount = Position / g_DataBufferLength;
	index CurrentPos = Position % g_DataBufferLength;

	if (_IsBuffered && BlocksCount != _Index){
		_FileArchive->write(&_Buffer, _IndexBuffer[_Index]);
		_IsBuffered = false;
	}


	if (_IndexBuffer.size() <= BlocksCount){
		_FileArchive->getBlocks(_IndexBuffer, _IndexBuffer.size());
		if (_IndexBuffer.size() <= BlocksCount){
			BlocksCount = _IndexBuffer.size() - 1;
			CurrentPos = BlocksCount * g_DataBufferLength + g_DataBufferLength;
		}
	}

	if (BlocksCount != _Index){
		_Index = BlocksCount;
		_FileArchive->read(&_Buffer, _IndexBuffer[_Index]);
	}

	_CurrentPos = CurrentPos < _Buffer._End ? CurrentPos : _Buffer._End;
	return(_Index * g_DataBufferLength + _CurrentPos);
}

void CFileHandler::close()
{
	if (_IsBuffered){
		_FileArchive->write(&_Buffer, _IndexBuffer[_Index]);
		_IsBuffered = false;
	}
}

void CFileHandler::setEOF()
{
	if (_Buffer._Next){
		while (_IndexBuffer.size() > _Index + 1){
			_IndexBuffer.pop_back();
		}
		_FileArchive->free(_Buffer._Next);
		_Buffer._Next = 0;
	}
	_Buffer._End = _CurrentPos;
	_IsBuffered = true;
}

CFileArchive::CFileArchive(FILE* Stream, const wchar_t* ArchivePath, bool& IsOk) : _Stream(Stream), _LockedCount(1), _ArchivePath(ArchivePath)
{
	IsOk = false;
	CBlock Block;
	if (fread(this, sizeof(CFileArchiveHeader), 1, _Stream) < 1){
		fwrite(this, sizeof(CFileArchiveHeader), 1, _Stream);
		
		fwrite(&Block, sizeof(CBlock), 1, _Stream);
		fflush(_Stream);
	}

	if (_Signature != FILEARCHIVE_SIGNATURE){
		assert(false);
		return;
	}
	fseek(_Stream, 0, SEEK_END);
	long ArchiveSize = ftell(_Stream);

	long SizeOffset = ArchiveSize - (long)(_TotalCount * sizeof(CBlock)+sizeof(CFileArchiveHeader));
	if (SizeOffset > 0){
		if (SizeOffset % sizeof(CBlock) > 0){
			assert(false);
			return;
		}
		index FreeBlockCount = (index)(SizeOffset / sizeof(CBlock));
		_TotalCount += FreeBlockCount;
		CBlock Block;
		fseek(_Stream, -sizeof(CBlock), SEEK_CUR);
		for (index i = _TotalCount - 1; FreeBlockCount > 0; i--, FreeBlockCount--){
			fwrite(&_FreeBlockIndex, sizeof(index), 1, _Stream);
			_FreeBlockIndex = i;
			fseek(_Stream, -(sizeof(CBlock)+sizeof(index)), SEEK_CUR);
		}
		fseek(_Stream, 0, SEEK_SET);
		fwrite(this, sizeof(CFileArchiveHeader), 1, _Stream);
		fflush(_Stream);
	}
	else if (SizeOffset < 0){
		assert(false);
		return;
	}

	_FileInfoHandler = new CFileInfoHandler(this);

	IsOk = true;
}

CFileArchive::~CFileArchive()
{

}

CFileArchive* CFileArchive::openArchive(const char* ArchivePath, bool createIfNoExist)
{
	std::string cPath(ArchivePath);
	std::wstring wPath(cPath.begin(), cPath.end());

	return openArchive(wPath, createIfNoExist);
}

CFileArchive* CFileArchive::openArchive(const wchar_t *ArchivePath, bool createIfNoExist)
{
	std::wstring Path(ArchivePath);
	return openArchive(Path, createIfNoExist);
}

CFileArchive* CFileArchive::openArchive(const std::wstring& ArchivePath, bool createIfNoExist)
{
	for (size_t i = 0; i < _Archives.size(); i++){
		if (ArchivePath == _Archives[i]->_ArchivePath){
			_Archives[i]->_LockedCount++;
			return _Archives[i];
		}
	}
	FILE* Stream = _wfopen(ArchivePath.c_str(), L"rb+");
	if (!Stream && createIfNoExist){
		Stream = _wfopen(ArchivePath.c_str(), L"wb+");
	}

	if (!Stream){
		return 0;
	}
	else{
		bool IsOk = true;
		CFileArchive* Archive = new CFileArchive(Stream, ArchivePath.c_str(), IsOk);
		if (IsOk){
			_Archives.push_back(Archive);
			return Archive;
		}
		else{
			delete Archive;
			return 0;
		}
	}
}

void CFileArchive::close()
{
	seekToStart();
	fwrite(this, sizeof(CFileArchiveHeader), 1, _Stream);

	_LockedCount--;
	if (_LockedCount == 0)	{
		_FileInfoHandler->close();
		fclose(_Stream);
		_Stream = 0;
	}
}

index CFileArchive::read(CBlock* Buffer, index BlockIndex)
{
	if (BlockIndex >= _TotalCount){
		memset(Buffer, 0, sizeof(CBlock));
		return 0;
	}
	seek(BlockIndex);
	return fread(Buffer, sizeof(CBlock), 1, _Stream);
}

index CFileArchive::write(CBlock* Buffer, index BlockIndex, bool GetNext /*= false*/)
{
	index NextBlock = Buffer->_Next;
	if (GetNext){
		if (NextBlock == 0){
			Buffer->_Next = _FreeBlockIndex;
			seek(BlockIndex);
			fwrite(Buffer, sizeof(CBlock), 1, _Stream);
			fflush(_Stream);
			return newBlock(Buffer);
		}
		else{
			seek(BlockIndex);
			fwrite(Buffer, sizeof(CBlock), 1, _Stream);
			seek(Buffer->_Next);
			fread(Buffer, sizeof(CBlock), 1, _Stream);
			return NextBlock;
		}
	}
	else{
		seek(BlockIndex);
		fwrite(Buffer, sizeof(CBlock), 1, _Stream);
		return BlockIndex;
	}
}

void CFileArchive::saveHeader(index FreeBlock)
{
	_FreeBlockIndex = FreeBlock;
	seek(g_MaxBlockIndex);
	fwrite(this, sizeof(CFileArchiveHeader), 1, _Stream);
}

index CFileArchive::newBlock(CBlock* Buffer)
{
	assert(!(_TotalCount == g_MaxBlockIndex));

	index BlockIndex = _FreeBlockIndex;
	if (_FreeBlockIndex < _TotalCount){
		seek(_FreeBlockIndex);
		fread(&_FreeBlockIndex, sizeof(index), 1, _Stream);
	}
	else{
		_FreeBlockIndex = (++_TotalCount);
	}

	memset(Buffer, 0, sizeof(CBlock));
	return BlockIndex;
}

void CFileArchive::seekToStart()
{
	fflush(_Stream);
	fseek(_Stream, 0, SEEK_SET);
}

void CFileArchive::seek(index BlockIndex)
{
	fflush(_Stream);
	fseek(_Stream, sizeof(CFileArchiveHeader)+sizeof(CBlock)* BlockIndex, SEEK_SET);
}

index CFileArchive::getBlocks(std::vector<index>& IndexBuffer, index BlockCount)
{
	index NextBlock = 0;
	index RequestCount = BlockCount;
	while (BlockCount > 0 && fseek(_Stream, blockOffset(IndexBuffer.back()), SEEK_SET) == 0){
		if (fread(&NextBlock, sizeof(index), 1, _Stream) == 1){
			if (NextBlock){
				IndexBuffer.push_back(NextBlock);
			}
			else{
				return(RequestCount - BlockCount);
			}
		}
		BlockCount--;
	}
	return(RequestCount - BlockCount);
}

CFileHandler* CFileArchive::open(const char* FilePath, bool createIfNoExist)
{
	index Index;
	CFileInfoHandler& FileInfoHandler = *_FileInfoHandler;
	if (FileInfoHandler.get(FilePath, Index)){
		return new CFileHandler(this, FileInfoHandler[Index]._FirstBlock, &FileInfoHandler[Index]);
	}
	else if (createIfNoExist && FileInfoHandler.add(FilePath, _FreeBlockIndex, Index)){
		if (_FreeBlockIndex < _TotalCount){
			seek(_FreeBlockIndex);
			fread(&_FreeBlockIndex, sizeof(index), 1, _Stream);
			if (_FreeBlockIndex == 0){
				_FreeBlockIndex = (++_TotalCount);
			}
		}
		else{
			_FreeBlockIndex = (++_TotalCount);
		}
		return new CFileHandler(this, FileInfoHandler[Index]._FirstBlock, &FileInfoHandler[Index]);
	}
	return 0;
}

const std::vector<CFileInfo>& CFileArchive::getFileInfoBuffer()
{
	return _FileInfoHandler->_FileInfoBuffer;
}

void CFileArchive::free(index BlockIndex)
{
	if (BlockIndex != 0 && BlockIndex != _TotalCount){
		index FreeBlockIndex = _FreeBlockIndex;
		_FreeBlockIndex      = BlockIndex;
		index LastBlock      = BlockIndex;
		index NextBlocK      = BlockIndex;
		seek(BlockIndex);
		while (fread(&NextBlocK, sizeof(index), 1, _Stream) == 1 && NextBlocK != 0){
			LastBlock = NextBlocK;
			seek(NextBlocK);
		}
		seek(LastBlock);
		fwrite(&FreeBlockIndex, sizeof(index), 1, _Stream);
	}
}



CFileInfoHandler::CFileInfoHandler(CFileArchive* Archive) : CFileHandler(Archive, 0, 0)
{
	if (read(&_Header, sizeof(CFileInfoHeader)) == sizeof(CFileInfoHeader)){
		if (_Header._TotalCount > 0){
			_FileInfoBuffer.reserve(_Header._TotalCount);
			read(_FileInfoBuffer._Myfirst, _Header._TotalCount * sizeof(CFileInfo));
			_FileInfoBuffer._Mylast = _FileInfoBuffer._Myend;
		}
	}
}

bool CFileInfoHandler::add(const char* Path, index BlockIndex, index& Index)
{
	CFileInfo Info;
	strcpy(Info._Path, Path);
	Info._FirstBlock = BlockIndex;
	if (_Header._FreeIndex < _Header._TotalCount){
		Index = _Header._FreeIndex;
		CFileInfo& NewInfo = _FileInfoBuffer[Index];
		_Header._FreeIndex = NewInfo._NextBlock;
		memmove(&NewInfo, &Info, sizeof(CFileInfo));
		if (_Header._FreeIndex == 0){
			_Header._FreeIndex = _Header._TotalCount;
		}
	}
	else{
		_FileInfoBuffer.push_back(Info);
		 Index  = _Header._TotalCount;
		_Header._FreeIndex = (++_Header._TotalCount);
	}
	setPos(0);
	write(&_Header, sizeof(CFileInfoHeader));
	setPos(sizeof(CFileInfoHeader) + Index * sizeof(CFileInfo));
	write(&Info, sizeof(CFileInfo));
	return true;
}


bool CFileInfoHandler::get(const char* Path, index& Index)
{
	for (index i = 0; i < _FileInfoBuffer.size(); i++){
		if (strcmp(_FileInfoBuffer[i]._Path, Path) == 0){
			Index = i;
			return true;
		}
	}
	return false;
}

bool CFileInfoHandler::remove(const char* Path)
{
	for (index i = 0; i < _FileInfoBuffer.size(); i++){
		if (strcmp(_FileInfoBuffer[i]._Path, Path) == 0){
			// uraditi nesto :)
			return true;
		}
	}
	return false;
}
